package JVM;


import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * OOM 内存不足异常
 * java.lang.StackOverflowError//栈溢出
 * java.lang.OutOfMemoryError:Java heap space//堆溢出
 * java.lang.OutOfMemoryError: GC overhead limit exceeded//GC效率太低
 * java.lang.OutOfMemoryError:Direct buffer memory//直接缓冲区内存溢出
 * java.lang.OutOfMemoryError:unable to create new native thread//linux里  一个进程默认1024
 * java.lang.OutOfMemoryError:Metaspace//原空间溢出  spring重用
 *
 * java.lang.OutOfMemoryError: Overflow: String length out of range//字符串长度溢出
 *
 */


public class OOM的几种异常 {
    public static void main(String[] args) {
        OutOfMemoryError_Direct_buffer_memory();

    }

    private static void OutOfMemoryError_Metaspace(String[] args) {
        /**
         * -XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=8m
         * -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m
         *
         * Error occurred during initialization of VM      初始化VM时出错
         * MaxMetaspaceSize is too small.                   MaxMetaspaceSize太小。
         * 8M有点小   大点
         *
         * java.lang.OutOfMemoryError-->Metaspace
         */
        int i=0;//记录模拟多少次发生异常
        try{
            while (true){
                i++;
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(MetaspaceError.class);
                enhancer.setUseCache(false);
                enhancer.setCallback(new MethodInterceptor() {
                    @Override
                    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                        return methodProxy.invokeSuper(o,args);
                    }
                });
                enhancer.create();
            }
        }catch (Throwable throwable){
            System.out.println("创建了"+i+"次");
            throwable.printStackTrace();
        }
    } //原空间内存溢出

    private static void OutOfMemoryError_unable_to_create_new_native_thread() {
        /**
         * 在linux中才行
         * 一般关不了
         *
         * 1. ps -ef|grep java//查看Java进程
         * 2.kill -9 进程号//杀死进程
         *
         * 增加linux的进程默认线程数（1024）
         * 非root用户1024  root无上限
         * 1.u limit -u//查看默认进程默认线程数（1024）
         * 2.vim /etc/security/limits.d/90-nproc.conf//打开文件修改
         *
         * unable_to_create_new_native_thread
         * 无法      创建    新的  本机    线程
         *
         */
        new Thread(()->{

        for (int i = 0; ; i++) {
            System.out.println("*********i=" + i);
            new Thread(()->{
                try {Thread.sleep(Integer.MAX_VALUE);}catch (InterruptedException e){e.printStackTrace();}
            },""+i).start();
        }
        },"6666").start();
    }  //进程中线程太多 溢出

    private static void OutOfMemoryError_Java_heap_space() {
        /**
         * 配置
         * -Xms10m -Xmx10m -XX:+PrintGCDetails
         * -XX:+PrintGCDetails 查看垃圾回收细节（日志）
         */
        byte[] bytes=new byte[80*1024*1024];
        //或
        String str = "java.lang.OutOfMemoryError";
        while (true){
            str+= str+new Random().nextInt(11111111)+new Random().nextInt(2222222);
            str.intern();
        }

    }  //堆溢出.

    private static void OutOfMemoryError_Direct_buffer_memory() {
        /**
         * JDK8  16没了
         */
        System.out.println("查看配置的本地内存maxDirectMemory:"+(sun.misc.VM.maxDirectMemory()>>20) +"MB");
        //停顿一下方便看效果
        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //-XX:MaxDirectMemorySize=5m 本地内存配置的是5MB,这里实际使用的是6MB
        ByteBuffer bb = ByteBuffer.allocateDirect(6 * 1024 * 1024);//jvm以外 分配内存（总的1/4）以内
        /**
         * -Xms10m -Xmx10m -XX:+PrintGCDetails  -XX:MaxDirectMemorySize=5m //最大堆内存
         * 创建Buffer对象时，可以选择从JVM堆中分配内存，也可以OS本地内存中分配，
         * 由于本地缓冲区避免了缓冲区复制，在性能上相对堆缓冲区有一定优势，但同时也存在一些弊端。
         *
         * 两种缓冲区对应的API如下：
         *      JVM堆缓冲区：ByteBuffer.allocate(size)
         *      本地缓冲区：ByteBuffer.allocateDirect(size)
         * 从堆中分配的缓冲区为普通的Java对象，生命周期与普通的Java对象一样，
         * 当不再被引用 时，Buffer对象会被回收。
         * 而直接缓冲区（DirectBuffer）为本地内存，并不在Java堆中，也不能被JVM垃圾回收。
         * 由于直接缓冲区在 JVM里被包装进Java对象DirectByteBuffer中，
         * 当它的包装类被垃圾回收时，会调用相应的JNI方法释放本地内存，
         * 所以本地内存的释放 也依赖于JVM中DirectByteBuffer对象的回收。
         *
         * 写NIo程序经常使ByteBuffer来读取或者写入数据，这是--种基于通道(Channel)与缓冲区(Buffer)的I/0方式，
         * 它可以使用Native函数库直接分配堆外内存，然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。
         * 这样能在一些场景中显著提高性能，因为避免了在Java堆和Native堆中来回复制数据。
         * ByteBuffer.allocate(capability)第一种 方式是分配JVM堆内存，属FGC管辖范围，由于需要拷贝所以速度相对较慢
         * ByteBuffer.allocateDirect(capability)第--种 方式是分配OS本地内存，不属FGc管辖范围，由于不需要内存拷贝所以速度相对较快。
         * 但如果不断分配本地内存，堆内存很少使用，那么JVM就不需要执GC, DirectByteBuffer. 对象们就不会被回收,
         * 这时候堆内存充足，但本地内存可能已经使用光了，再次尝试分配本地内存就会出现OutOfMemoryError。,那程序就直接崩溃了。
         *
         * 由于垃圾回收本身成本较高，一般JVM在堆内存未耗尽时，不会进行垃圾回收操作。
         * 我们知道在32位机器上，每个进程的最大可用内存为4G，
         * 用户可用 内存在大概为3G左右，如果为堆分配过大的内存时，
         * 本地内存可用空间就会相应减少。当我们为堆分配较多的内存时，
         * JVM可能会在相当长的时间内不会进行垃圾回收操作，从而本地内存不断分配，
         * 无法释放，最终导致OutOfMemoryError。
         *
         *
         * 由此可见，在使用直接缓冲区之前，需要做出权衡：
         *      1.堆缓冲区的性能已经相当高，若无必要，使用堆缓冲区足矣。
         *        若确实有提升性能的必要时，再考虑使用本地缓冲区。
         *      2.为JVM分配堆内存时，并不是越大越好，堆内存越大，本地内存就越小，
         *        根据具体情况决定，主要针对32位机器，64位机器上不存在该问题。
         *
         */
//        System.out.println("配置的maxDirectMemory:"+(sun.misc.VM.maxDirectMemory()>>20)+"MB");
//        ByteBuffer buffer = ByteBuffer.allocateDirect( 6 << 20 );//jvm以外 分配内存（总的1/4）以内
//        ByteBuffer allocate = ByteBuffer.allocate(6<<20);//jvm内
    } //直接缓冲区内存  JDK8会出现   16暂时不知道

    private static void OutOfMemoryError_GC_overhead_limit_exceeded() {
        /**
         * 配置
         * -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+UseParallelGC（并行垃圾回收）
         *  -XX:MaxDirectMemorySize=5m  //最大直接内存化   即最大使用的内存 可有可无
         *
         *  OutOfMemoryError是java.lang.VirtualMachineError的子类，
         *  当JVM资源利用出现问题时抛出，
         *  更具体地说，这个错误是由于JVM花费太长时间执行GC且只能回收很少的堆内存时抛出的。
         *  根据Oracle官方文档，默认情况下，
         *  如果Java进程花费98%以上的时间执行GC，
         *  并且每次只有不到2%的堆被恢复，
         *  则JVM抛出此错误。换句话说，这意味着我们的应用程序几乎耗尽了所有可用内存，
         *  垃圾收集器花了太长时间试图清理它，并多次失败。
         *
         *
         */
        int i=0;
        List<String> list=new ArrayList<>();
        try {
            while (true){
                list.add(String.valueOf(++i).intern());
            }
        }catch (Throwable e){
            System.out.println("**************"+i);
            e.printStackTrace();//Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
            throw  e;
        }
//        Map<Integer, String> dataMap = new HashMap<>();
//        Random r = new Random();
//        while (true) {
//            dataMap.put(r.nextInt(), String.valueOf(r.nextInt()));
//        }
    }//如果Java进程花费98%以上的时间执行GC，并且每次只有不到2%的堆被恢复，

    static class MetaspaceError{ }


}
